﻿using System;
using System.Collections.Generic;
using SlimDX.Direct3D11;
using SlimDX.D3DCompiler;
using SlimDX;
using SlimDX.DXGI;
using Device = SlimDX.Direct3D11.Device;
using Resource = SlimDX.Direct3D11.Resource;
using Buffer = SlimDX.Direct3D11.Buffer;
using System.Drawing;


namespace InsTron
{
    class PlayerView
    {
        private PlayerViewCommons commons;
        public Camera camera;
        private DeviceContext deferredContext;
        private RenderTargetView targetView;
        private RenderTargetView reflectionTargetView;
        private DepthStencilView depthView;
        private Texture2D depthBuffer;
        private Texture2D targetTexture;
        private Texture2D reflectionTexture;

        public Texture2D TargetTexture
        {
            get { return targetTexture; }
        }


        public PlayerView(PlayerViewCommons commons, float shiftX = 0.0f, float shiftY = 0.0f)
        {
            this.commons = commons;
            InitializeAll(shiftX, shiftY);
        }


        private void InitializeAll(float shiftX, float shiftY)
        {
            camera = new Camera();
            deferredContext = new DeviceContext(commons.Device);
            deferredContext.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
            InitializeTargets(shiftX, shiftY);
            InitializeDepth();
            SetUpCamera();
            PrepareScene();
            deferredContext.OutputMerger.SetTargets(depthView, targetView);
        }


        public void DisposeAll()
        {
            targetTexture.Dispose();
            reflectionTexture.Dispose();
            depthBuffer.Dispose();
            depthView.Dispose();            
            targetView.Dispose();
            reflectionTargetView.Dispose();
            deferredContext.Dispose();
        }


        private void InitializeTargets(float shiftX, float shiftY)
        {
            targetTexture = new Texture2D(commons.Device, commons.TargetTextureDescription);
            reflectionTexture = new Texture2D(commons.Device, commons.ReflectionTextureDescription);

            targetView = new RenderTargetView(commons.Device, targetTexture);
            reflectionTargetView = new RenderTargetView(commons.Device, reflectionTexture);

            if (Settings.SbaaEnabled)
            {
                shiftX = shiftY = 0.25f;
            }
            deferredContext.Rasterizer.SetViewports(
                new Viewport(shiftX, shiftY, Settings.WorkingResolution.Width + shiftX, Settings.WorkingResolution.Height + shiftY));
        }


        private void InitializeDepth()
        {
            depthBuffer = new Texture2D(commons.Device, commons.DepthTextureDescription);
            depthView = new DepthStencilView(commons.Device, depthBuffer);
            deferredContext.OutputMerger.DepthStencilState = commons.DepthState;
        }


        public void SetUpCamera()
        {
            const float dist = 128;
            switch (Constants.SCENE)
            {
                case 1:
                    camera.Position = new Vector3(6.0f, 3.0f, -16);
                    camera.Target = new Vector3(0.0f, 0.0f, 0.0f);
                    break;
                case 2:
                    camera.Position = new Vector3(-3.0f, 3.0f, -2.0f);
                    camera.Target = new Vector3(0.0f, 0.0f, 0.0f);
                    break;
                case 3:                    
                    camera.Position = new Vector3(-dist, dist*1.5f, -dist);
                    camera.Target = new Vector3(0.0f, 0.0f, 0.0f);
                    break;
                case 4:
                    camera.Position = new Vector3(-dist, dist * 1.5f, -dist);
                    camera.Target = new Vector3(0.0f, 0.0f, 0.0f);
                    break;
                case 5:
                    camera.Target = new Vector3(0.0f, 8.0f, 0.0f);
                    break;
                case 6:
                    camera.Position = new Vector3(6.0f, 3.0f, -16);
                    camera.Target = new Vector3(0.0f, 0.0f, 0.0f);
                    break;
            }
        }


        private void PrepareScene()
        {
            if (Settings.PlayerInitialized)
                return;

            commons.Effect.GetVariableByName("xViewProjectionMatrix").AsMatrix().SetMatrix(camera.CameraView * commons.Perspective);
            commons.Effect.GetVariableByName("xCameraPosition").AsVector().Set(camera.Position);
            switch (Constants.SCENE)
            {
                case 1:
                    Settings.PlatformReflection = false;
                    commons.Technique_Bike = commons.Effect.GetTechniqueByName("BikeNoTex");
                    commons.Technique_PlatformNoReflection = commons.Effect.GetTechniqueByName("PlatformNoReflectionNoTex");
                    commons.Technique_Wall = commons.Effect.GetTechniqueByName("WallNoAlpha");
                    commons.Effect.GetVariableByName("xLightKnob").AsScalar().Set(0.0f);
                    commons.GameState.Advance(3680);
                    commons.GameState.InitiateTurnPlayer(0, true);
                    commons.GameState.InitiateTurnPlayer(1, true);
                    commons.GameState.Advance(200);
                    commons.GameState.AdvancePlayer(1600, 0);
                    commons.GameState.InitiateTurnPlayer(0, true);
                    commons.GameState.InitiateTurnPlayer(1, true);
                    commons.GameState.Advance(200);
                    commons.GameState.AdvancePlayer(160, 1);
                    commons.GameState.InitiateTurnPlayer(0, true);
                    commons.GameState.InitiateTurnPlayer(1, false);
                    commons.GameState.Advance(360);
                    commons.GameState.AdvancePlayer(720, 0);
                    commons.GameState.InitiateTurnPlayer(0, false);
                    commons.GameState.AdvancePlayer(320, 0);
                    break;
                case 2:
                    break;
                case 3:
                    Settings.PlatformReflection = false;
                    commons.Effect.GetVariableByName("xLightKnob").AsScalar().Set(0.0f);
                    using (var resource = new ShaderResourceView(commons.Device, commons.MoireTexture))
                        commons.Effect.GetVariableByName("xPlatformTexture").AsResource().SetResource(resource);
                    if (Settings.MipMapping)
                    {
                        if (Settings.SupersamplingFactor == 1)
                        {
                            commons.Technique_PlatformNoReflection = commons.Effect.GetTechniqueByName("PlatformNoReflectionMipMap");
                        }
                        else
                        {
                            commons.Technique_PlatformNoReflection = commons.Effect.GetTechniqueByName("PlatformNoReflectionMipMapSS");
                        }
                    }
                    else
                    {
                        commons.Technique_PlatformNoReflection = commons.Effect.GetTechniqueByName("PlatformNoReflection");
                    }
                    break;
                case 4:
                    Settings.PlatformReflection = false;
                    commons.Effect.GetVariableByName("xLightKnob").AsScalar().Set(0.0f);
                    using (var resource = new ShaderResourceView(commons.Device, commons.MoireTexture))
                        commons.Effect.GetVariableByName("xPlatformTexture").AsResource().SetResource(resource);
                    if (Settings.MipMapping)
                    {
                        if (Settings.SupersamplingFactor == 1)
                        {
                            commons.Technique_PlatformNoReflection = commons.Effect.GetTechniqueByName("PlatformNoReflectionMipMap");
                        }
                        else
                        {
                            commons.Technique_PlatformNoReflection = commons.Effect.GetTechniqueByName("PlatformNoReflectionMipMapSS");
                        }
                    }
                    else
                    {
                        commons.Technique_PlatformNoReflection = commons.Effect.GetTechniqueByName("PlatformNoReflection");
                    }
                    break;
                case 5:
                    commons.Technique_PlatformNoReflection = commons.Effect.GetTechniqueByName("PlatformNoReflectionNoTex");
                    break;
                case 6:
                    commons.GameState.Advance(3680);
                    commons.GameState.InitiateTurnPlayer(0, true);
                    commons.GameState.InitiateTurnPlayer(1, true);
                    commons.GameState.Advance(200);
                    commons.GameState.AdvancePlayer(1600, 0);
                    commons.GameState.InitiateTurnPlayer(0, true);
                    commons.GameState.InitiateTurnPlayer(1, true);
                    commons.GameState.Advance(200);
                    commons.GameState.AdvancePlayer(160, 1);
                    commons.GameState.InitiateTurnPlayer(0, true);
                    commons.GameState.InitiateTurnPlayer(1, false);
                    commons.GameState.Advance(360);
                    commons.GameState.AdvancePlayer(720, 0);
                    commons.GameState.InitiateTurnPlayer(0, false);
                    commons.GameState.AdvancePlayer(320, 0);
                    Settings.PlatformReflection = true;
                    break;
            }
            Settings.PlayerInitialized = true;
        }


        public void RenderScene(DeviceContext immediateContext)
        {
            switch (Constants.SCENE)
            {
                case 1:
                    RenderScene1(immediateContext);
                    break;
                case 2:
                    RenderScene2(immediateContext);
                    break;
                case 3:
                    RenderScene3(immediateContext);
                    break;
                case 4:
                    RenderScene4(immediateContext);
                    break;
                case 5:
                    RenderScene5(immediateContext);
                    break;
                case 6:
                    RenderScene6(immediateContext);
                    break;
            }
        }


        private void RenderScene1(DeviceContext immediateContext)
        {
            CommandList commandList = RenderFinalScene();
            immediateContext.ExecuteCommandList(commandList, true);
            commandList.Dispose();
        }


        private void RenderScene2(DeviceContext immediateContext)
        {
            ClearView(targetView);

            deferredContext.InputAssembler.InputLayout = commons.InputLayout_PostTexNorm;

            RenderSky();
            RenderCubes();

            CommandList commandList = deferredContext.FinishCommandList(true);
            immediateContext.ExecuteCommandList(commandList, true);
            commandList.Dispose();
        }


        private void RenderScene3(DeviceContext immediateContext)
        {
            ClearView(targetView);

            deferredContext.InputAssembler.InputLayout = commons.InputLayout_PostTexNorm;

            RenderSky();
            RenderPlatform();

            CommandList commandList = deferredContext.FinishCommandList(true);
            immediateContext.ExecuteCommandList(commandList, true);
            commandList.Dispose();
        }


        private void RenderScene4(DeviceContext immediateContext)
        {
            ClearView(targetView);

            deferredContext.InputAssembler.InputLayout = commons.InputLayout_PostTexNorm;

            RenderSky();
            RenderPlatform();

            CommandList commandList = deferredContext.FinishCommandList(true);
            immediateContext.ExecuteCommandList(commandList, true);
            commandList.Dispose();
        }


        private void RenderScene5(DeviceContext immediateContext)
        {
            ClearView(targetView);

            deferredContext.InputAssembler.InputLayout = commons.InputLayout_PostTexNorm;


            camera.Position = new Vector3(-Constants.PlatformSize * 2.4f, 12.0f, -Constants.PlatformSize * 2.4f);
            camera.Up = new Vector3(0f, 1f, 0f);
            commons.Effect.GetVariableByName("xViewProjectionMatrix").AsMatrix().SetMatrix(camera.CameraView * commons.Perspective);
            commons.Effect.GetVariableByName("xCameraPosition").AsVector().Set(camera.Position);
            RenderSky();
            RenderPlatform();


            camera.Position = new Vector3(0.0f, 12.0f, -Constants.PlatformSize * 2.6f);
            camera.Up = new Vector3(0f, -1f, 0f);
            commons.Effect.GetVariableByName("xViewProjectionMatrix").AsMatrix().SetMatrix(camera.CameraView * commons.Perspective);
            commons.Effect.GetVariableByName("xCameraPosition").AsVector().Set(camera.Position);
            RenderPlatform();

            CommandList commandList = deferredContext.FinishCommandList(true);
            immediateContext.ExecuteCommandList(commandList, true);
            commandList.Dispose();
        }


        private void RenderScene6(DeviceContext immediateContext)
        {
            CommandList commandList;

            commandList = RenderSceneReflection();
            immediateContext.ExecuteCommandList(commandList, true);
            commandList.Dispose();

            commandList = RenderFinalScene();
            immediateContext.ExecuteCommandList(commandList, true);
            commandList.Dispose();
        }


        public CommandList RenderSceneReflection()
        {
            deferredContext.OutputMerger.SetTargets(depthView, reflectionTargetView);

            ClearView(reflectionTargetView);

            commons.Effect.GetVariableByName("xViewProjectionMatrix").AsMatrix().SetMatrix(camera.InvertedCamera.CameraView * commons.Perspective);
            commons.Effect.GetVariableByName("xCameraPosition").AsVector().Set(camera.InvertedCamera.Position);

            RenderObjects();

            return deferredContext.FinishCommandList(true);
        }


        public CommandList RenderFinalScene()
        {      
            ClearView(targetView);

            if (Settings.PlatformReflection)
            {
                deferredContext.OutputMerger.SetTargets(depthView, targetView);
                commons.Effect.GetVariableByName("xViewProjectionMatrix").AsMatrix().SetMatrix(camera.CameraView * commons.Perspective);
                commons.Effect.GetVariableByName("xCameraPosition").AsVector().Set(camera.Position);
            }

            RenderPlatform();
            RenderObjects();

            return deferredContext.FinishCommandList(true);
        }


        private void ClearView(RenderTargetView targetViewToClear)
        {
            deferredContext.ClearRenderTargetView(targetViewToClear, Color.Black);
            deferredContext.ClearDepthStencilView(depthView, DepthStencilClearFlags.Depth, 1, 0);
        }


        private void RenderObjects()
        {
            deferredContext.InputAssembler.InputLayout = commons.InputLayout_PostTexNorm;

            RenderSky();
            RenderBike(0);
            RenderBike(1);

            deferredContext.InputAssembler.InputLayout = commons.InputLayout_PostNorm;

            SortAndRenderWalls();
        }


        private void RenderPlatform()
        {
            deferredContext.InputAssembler.InputLayout = commons.InputLayout_PostTexNorm;
            if (Settings.PlatformReflection)
            {
                using (var resource = new ShaderResourceView(commons.Device, reflectionTexture))
                    commons.Effect.GetVariableByName("xReflectionTexture").AsResource().SetResource(resource);
                commons.PlatformModel.Render(deferredContext, commons.Technique_Platform);
            }
            else
            {
                commons.PlatformModel.Render(deferredContext, commons.Technique_PlatformNoReflection);
            }
        }


        private void RenderSky()
        {
            commons.Effect.GetVariableByName("xSkySphereTranslationMatrix").AsMatrix().SetMatrix(Matrix.Translation(camera.Position));
            
            commons.Effect.GetVariableByName("xSkySphereRotationMatrix").AsMatrix().SetMatrix(commons.OuterSkyRotationMatrix);
            commons.Effect.GetVariableByName("xSkySphereScalingMatrix").AsMatrix().SetMatrix(Matrix.Scaling(1.1f, 1.1f, 1.1f));
            commons.Effect.GetVariableByName("xSkyColor").AsVector().Set(Constants.OuterSkyColor);
            commons.SkySphereModel.Render(deferredContext, commons.Technique_Sky);

            commons.Effect.GetVariableByName("xSkySphereRotationMatrix").AsMatrix().SetMatrix(commons.InnerSkyRotationMatrix);
            commons.Effect.GetVariableByName("xSkySphereScalingMatrix").AsMatrix().SetMatrix(Matrix.Scaling(0.9f, 0.9f, 0.9f));
            commons.Effect.GetVariableByName("xSkyColor").AsVector().Set(Constants.InnerSkyColor);
            commons.SkySphereModel.Render(deferredContext, commons.Technique_Sky);
        }


        private void RenderBike(int idx)
        {
            commons.Effect.GetVariableByName("xBikeTranslationMatrix").AsMatrix().SetMatrix(commons.GetBikeTranslationMatrix(idx));
            commons.Effect.GetVariableByName("xBikeRotationMatrix").AsMatrix().SetMatrix(commons.GetBikeRotationMatrix(idx));
            commons.Effect.GetVariableByName("xPlayerColor").AsVector().Set(new Color4(Settings.BikeParameters[idx].Color));
            commons.BikeModel.Render(deferredContext, commons.Technique_Bike, idx == 0);
        }


        private void RenderCubes()
        {
            RenderCube(2, Matrix.RotationX(-0.2f) * Matrix.Translation(2, 0.5f, 2.5f));
            RenderCube(1, Matrix.RotationY(0.2f) * Matrix.Translation(2, -3, -0.5f));
            RenderCube(0, Matrix.Identity);
        }


        private void RenderCube(int num, Matrix transformationMatrix)
        {
            commons.Effect.GetVariableByName("xNum").AsScalar().Set(num);
            commons.Effect.GetVariableByName("xCubeTransformationMatrix").AsMatrix().SetMatrix(transformationMatrix);
            commons.CubeModel.Render(deferredContext, commons.Technique_Cube);
        }


        private void SortAndRenderWalls()
        {
            Queue<WallSegment> AllWalls = new Queue<WallSegment>();
            EnqueueAllWalls(AllWalls);

            while (AllWalls.Count > 0)
            {
                bool canDraw = true;
                WallSegment segment = AllWalls.Dequeue();
                foreach (WallSegment s in AllWalls)
                {
                    if (IsCovered(s, segment))
                    {
                        canDraw = false;
                        AllWalls.Enqueue(segment);
                        break;
                    }
                }
                if (canDraw)
                {
                    RenderWallSegment(segment);
                    int i = segment.playerIdx;
                    i = 1 * 1;
                }
            }
        }


        private void EnqueueAllWalls(Queue<WallSegment> AllWalls)
        {
            for (int i = 0; i < 2; i++)
            {
                foreach (WallSegment segment in commons.GameState.PlayerWalls[i])
                {
                    AllWalls.Enqueue(segment);
                }
            }
        }


        private void RenderWallSegment(WallSegment segment)
        {
            commons.Effect.GetVariableByName("xPlayerColor").AsVector().Set(segment.GetColor());
            commons.Effect.GetVariableByName("xWallTransformationMatrix").AsMatrix()
                .SetMatrix(segment.GetTransformationMatrix());
            if (segment.IsHorizontal)
                commons.WallModelHorizontal.Render(deferredContext, commons.Technique_Wall);
            else
                commons.WallModelVertical.Render(deferredContext, commons.Technique_Wall);
        }


        private bool IsCovered(WallSegment what, WallSegment byWhat)
        {
            if (what.playerIdx == 0 && byWhat.playerIdx == 1)
                return true;
            if (what.playerIdx == 1 && byWhat.playerIdx == 0)
                return false;
            if (what.IsHorizontal && byWhat.IsHorizontal)
            {
                if (what.Begin.Z > camera.Position.Z && byWhat.Begin.Z > camera.Position.Z)
                {
                    return what.Begin.Z > byWhat.Begin.Z;
                }
                else if (what.Begin.Z < camera.Position.Z && byWhat.Begin.Z < camera.Position.Z)
                {
                    return what.Begin.Z < byWhat.Begin.Z;
                }
                else
                {
                    return false;
                }
            }
            if (!what.IsHorizontal && !byWhat.IsHorizontal)
            {
                if (what.Begin.X > camera.Position.X && byWhat.Begin.X > camera.Position.X)
                {
                    return what.Begin.X > byWhat.Begin.X;
                }
                else if (what.Begin.X < camera.Position.X && byWhat.Begin.X < camera.Position.X)
                {
                    return what.Begin.X < byWhat.Begin.X;
                }
                else
                {
                    return false;
                }
            }

            return false;
        }
    }
}
